# @Time  :2021/5/27 21:40
# @Author: Houtaroy
from typing import List
from dataclasses import dataclass
from decimal import Decimal
from datetime import datetime, date

from flask_restx import fields

from .database import db
from .exceptions import BusinessException
from .enums import YesNo
from .swagger import swagger
from .utils import AuthUtil


@dataclass
class Pageable:
    page: int
    size: int


@dataclass
class PageResult():
    items: List
    page: int
    pages: int
    per_page: int
    total: int


@dataclass(init=False)
class Entity(db.Model):
    __abstract__ = True

    id: int = db.Column(db.Integer, primary_key=True, autoincrement=True)

    @classmethod
    def _get_python_type(cls, column) -> type:
        if hasattr(column.type, 'impl'):
            if hasattr(column.type.impl, 'python_type'):
                return column.type.impl.python_type
            return type(column.type.impl)
        if hasattr(column.type, 'python_type'):
            return column.type.python_type
        return type(column.type)

    @classmethod
    def _python_to_flask(cls, python_type: type) -> str:
        if python_type is int:
            return 'Integer'
        if python_type in [float, Decimal]:
            return 'Float'
        if python_type is bool:
            return 'Boolean'
        if python_type is datetime:
            return 'DateTime'
        if python_type is date:
            return 'Date'
        return 'String'

    @classmethod
    def generate_swagger_model(cls):
        schema = {}
        for column in cls.__table__.columns:
            schema[column.name] = getattr(fields, cls._python_to_flask(cls._get_python_type(column)))(
                description=column.comment,
                required=not column.nullable
            )
        return swagger.model(cls.__name__, schema)

    @classmethod
    def search(cls, parameters: dict = None) -> list:
        """
        查询
        :param parameters: 条件
        :return: 数组结果
        """
        return cls.query.filter(*cls.generate_search_parameters(parameters)).all()

    @classmethod
    def page(cls, parameters: dict = None):
        """
        分页查询
        :param parameters: 条件
        :return: 分页结果
        """
        if parameters is None:
            parameters = {}
        pageable = parameters.get("pageable") or Pageable(1, 50)
        return cls.query.filter(*cls.generate_search_parameters(parameters)). \
            paginate(pageable.page, pageable.size, error_out=False)

    @classmethod
    def get(cls, key: int):
        """
        查看
        :param key: 主键id
        :return: 结果
        """
        result = cls.query.get(key)
        if result is None:
            raise BusinessException('资源不存在')
        return result

    @classmethod
    def add(cls, data: dict):
        """
        新增
        :return: 无
        """
        result = cls(**data)
        db.session.add(result)
        db.session.commit()
        return result

    @classmethod
    def update(cls, id, data):
        """
        更新
        :return: 无
        """
        old = cls.get(id)
        if old is None:
            raise BusinessException('资源不存在')
        for key, value in data.items():
            if hasattr(old, key):
                setattr(old, key, value)
        db.session.commit()

    @classmethod
    def delete(cls, key: int):
        """
        删除
        :param key: 主键id
        :return: 无
        """
        db.session.delete(cls.get(key))
        db.session.commit()

    @classmethod
    def generate_search_parameters(cls, parameters: dict = None):
        """
        生成查询条件
        目前只支持等于, 后续再研究如何加入其它
        :param parameters: 查询条件, 字典类型例如:{"name": "test01"}
        :return: sqlalchemy中filter支持的条件
        """
        result = []
        if parameters is None:
            return result
        for key, value in parameters.items():
            if value and hasattr(cls, key):
                result.append(getattr(cls, key) == value)
        return result

    def __hash__(self):
        return hash(f"{self.__class__.__module__}.{self.__class__.__name__}.{self.id}")


@dataclass(init=False)
class AuditEntity(Entity):
    __abstract__ = True

    is_delete: int = db.Column(db.Integer, default=0)
    create_time: datetime = db.Column(db.DateTime)
    create_user_id: int = db.Column(db.Integer)
    last_modify_time: datetime = db.Column(db.DateTime)
    last_modify_user_id: int = db.Column(db.Integer)
    delete_time: datetime = db.Column(db.DateTime)
    delete_user_id: int = db.Column(db.Integer)

    @classmethod
    def get_by_id(cls, key: int):
        """
        根据id查询, 包含已删除内容
        :param key: 主键id
        :return: 结果
        """
        return super().get(key)

    @classmethod
    def get(cls, key: int):
        """
        查看
        :param key: 主键id
        :return: 结果
        """
        result = cls.get_by_id(key)
        if result.is_delete == YesNo.YES.value:
            raise BusinessException('资源不存在')
        return result

    @classmethod
    def add(cls, data: dict):
        data['create_time'] = datetime.now()
        data['create_user_id'] = AuthUtil.current_user_id()
        return super().add(data)

    @classmethod
    def update(cls, id, data):
        data['last_modify_time'] = datetime.now()
        data['last_modify_user_id'] = AuthUtil.current_user_id()
        super().update(id, data)

    @classmethod
    def delete(cls, key: int):
        entity = cls.get(key)
        entity.is_delete = YesNo.YES.value
        db.session.commit()

    @classmethod
    def generate_search_parameters(cls, parameters: dict = None):
        """
        生成查询条件, 追加is_delete条件
        目前只支持等于, 后续再研究如何加入其它
        :param parameters: 查询条件, 字典类型例如:{"name": "test01"}
        :return: sqlalchemy中filter支持的条件
        """
        result = super().generate_search_parameters(parameters)
        result.append(cls.is_delete == YesNo.NO.value)
        return result
